package com.hmall.gateway.route;

import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.hmall.common.utils.CollUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {
    private final NacosConfigManager nacosConfigManager;   // 用于获取路由配置文件，同时添加监听器
    private final RouteDefinitionWriter writer;            // 用于更新路由
    private final String dataId = "gateway-routes.json";   // 网关路由配置文件
    private final String group = "DEFAULT_GROUP";          // 网关路由配置文件所在组
    private final Set<String> routeIds = new HashSet<>(); // 保存路由id

    // PostConstruct注解表示该方法会在构造函数之后执行，而该类使用@Component注解，则该类会在spring启动时被初始化，因此该方法会在Spring启动时执行
    @PostConstruct
    public void initRouteConfigListener() throws NacosException {
        // 获取配置文件，同时添加监听器
        String configAndSignListener = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(dataId, group, 5000, new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }

                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        // 监听到配置文件变化
                        log.info("监听到配置文件变化：{}", configInfo);
                        updateConfigInfo(configInfo);
                    }
                });
        // 首次启动时要加载一次路由配置
        updateConfigInfo(configAndSignListener);
    }

    private void updateConfigInfo(String configInfo) {
        // 将配置文件转换成RouteDefinition集合
        List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
        // 先清空原路由表中的所有路由
        for (String routeId : routeIds) {
            writer.delete(Mono.just(routeId)).subscribe();
        }
        // 清空路由id集合
        routeIds.clear();
        // 无新的路由配置，直接返回
        if(CollUtils.isEmpty(routeDefinitions))
            return;
        // 更新路由以及路由id集合
        for (RouteDefinition routeDefinition : routeDefinitions) {
            writer.save(Mono.just(routeDefinition)).subscribe();
            routeIds.add(routeDefinition.getId());
        }
    }
}

